﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using System.IO;
using Nini.Config;

using Microsoft.Data.Schema.SchemaModel;
using Microsoft.Data.Schema.ScriptDom.Sql;
using Microsoft.Data.Schema.Sql.SchemaModel;
using Microsoft.Data.Schema.Sql;
using Microsoft.Data.Schema.StaticCodeAnalysis;
using System.Text.RegularExpressions;



namespace Neznayka
{

    public static class DMVSettings
    {
        private static DateTime _LastNiniRefresh;
        private static int _CacheRefreshIntervalSeconds = 5;


        private static String[] _ExcludedParameterTypesForNameLengthRule;
        private static int      _MinimumParameterNameLength;

        private static String[] _ExcludedVariableTypesForNameLengthRule;
        private static int      _MinimumVariableNameLength;
        private static bool    _ExcludedVariableTypesForNameLengthRuleIncludesTable;

        private static string _ValidParameterAndVariableRegexp;
        private static Regex _ValidParameterAndVariableRegexpAsRegExp;


        private static IEnumerable<IModelElement> ts;
        private static IEnumerable<IModelElement> vs;

        private static IEnumerable<ISqlTable> tables;
        private static IEnumerable<ISqlView> views;

        //private static Dictionary<String, List<String>> _InsertStatementOptionalColumnsCache;
        //private static Dictionary<String, List<String>> _InsertStatementMandatoryColumnsCache;

        private static DateTime _LastInsertColumnCacheRefresh;

        //private static Dictionary<String, List<ISqlTable>> _tablesWithLogicalExclusionColumnCache;
        //private static Dictionary<String, List<ISqlView>> _viewsWithLogicalExclusionColumnCache;
        //private static DateTime _LastLogicalExclusionColumnCacheRefresh;

        private static Dictionary<String, List<ISqlSimpleColumn>> _tablesColumnsCache;

        private static IEnumerable<ISqlIndex> _IndexesCache;
        private static IEnumerable<ISqlPrimaryKeyConstraint> _PrimaryKeyConstraints;
        private static IEnumerable<ISqlForeignKeyConstraint> _ForeignKeyConstraints;
        private static IEnumerable<ISqlUniqueConstraint> _UniqueConstraints;
        private static DateTime _LastConstraintsAndIndexesCacheRefresh;

        private static bool _AllowClusterOnPrimaryKey = false;
        private static bool _AllowClusterOnForeignKey = false;

        private static int _MaximumNoOfIndexColumns = 0;


        #region properties

        public static String[] ExcludedParameterTypesForNameLengthRule
        {
            set { _ExcludedParameterTypesForNameLengthRule = value; }
            get { return (string[])_ExcludedParameterTypesForNameLengthRule.Clone(); }
        }
        public static int MinimumParameterNameLength
        {
            set { _MinimumParameterNameLength = value; }
            get { return _MinimumParameterNameLength; }
        }

        public static bool ExcludedVariableTypesForNameLengthRuleIncludesTable
        {
            set { _ExcludedVariableTypesForNameLengthRuleIncludesTable = value; }
            get { return _ExcludedVariableTypesForNameLengthRuleIncludesTable; }
        }
        public static String[] ExcludedVariableTypesForNameLengthRule
        {
            set { _ExcludedVariableTypesForNameLengthRule = value; }
            get { return (string[])_ExcludedVariableTypesForNameLengthRule.Clone(); }
        }
        public static int MinimumVariableNameLength
        {
            set { _MinimumVariableNameLength = value; }
            get { return _MinimumVariableNameLength; }
        }
        public static String ValidParameterAndVariableRegexp
        {
            set { _ValidParameterAndVariableRegexp = value;
                  _ValidParameterAndVariableRegexpAsRegExp = new Regex(_ValidParameterAndVariableRegexp);
            }
            get { return _ValidParameterAndVariableRegexp; }
        }
        public static Regex ValidParameterAndVariableRegexpAsRegExp
        {
            get { return _ValidParameterAndVariableRegexpAsRegExp; }
        }

        public static IEnumerable<ISqlTable> modelTables
        {
            get { return tables; }
        }
        public static IEnumerable<ISqlView> modelViews
        {
            get { return views; }
        }


        public static bool AllowClusterOnPrimaryKey
        {
            set { _AllowClusterOnPrimaryKey = value; }
            get { return _AllowClusterOnPrimaryKey; }
        }

        public static bool AllowClusterOnForeignKey
        {
            set { _AllowClusterOnForeignKey = value; }
            get { return _AllowClusterOnForeignKey; }
        }

        public static IList<ISqlIndex> getIndexes
        {
            get { return _IndexesCache.ToList().AsReadOnly(); }
        }
        public static IList<ISqlPrimaryKeyConstraint> getPrimaryKeys
        {
            get { return _PrimaryKeyConstraints.ToList().AsReadOnly(); }
        }
        public static IList<ISqlForeignKeyConstraint> getForeignKeys
        {
            get { return _ForeignKeyConstraints.ToList().AsReadOnly(); }
        }
        public static IList<ISqlUniqueConstraint> getUniqueConstraints
        {
            get { return _UniqueConstraints.ToList().AsReadOnly(); }
        }

        public static int MaximumNoOfIndexColumns
        {
            set { _MaximumNoOfIndexColumns = value; }
//            get { return (int)_MaximumNoOfIndexColumns.Clone(); }
            get { return (int)_MaximumNoOfIndexColumns; }
        }

        #endregion


        public static IList<ISqlSimpleColumn> tableColumns(string SchemaAndTableName)
        {
            if (_tablesColumnsCache.ContainsKey(SchemaAndTableName))
            {
                return _tablesColumnsCache[SchemaAndTableName].AsReadOnly();
            }
            else
            {
                List<ISqlSimpleColumn> x = new List<ISqlSimpleColumn>();
                return x;
            }
        }




        //        public static void RefreshConstraintsAndIndexesCache(DataSchemaModel ssm)
        public static void RefreshConstraintsAndIndexesCache(ModelStore ssm)
        {

            // in case initialiser doesn't work.
            if (_LastConstraintsAndIndexesCacheRefresh == null ||
                 DateTime.Compare(_LastConstraintsAndIndexesCacheRefresh.Add(TimeSpan.FromSeconds(_CacheRefreshIntervalSeconds)), DateTime.Now) == -1
               )
            {
                IEnumerable<IModelElement> idxs = ssm.GetElements(typeof(ISqlIndex), 0);
                IEnumerable<IModelElement> pkcs = ssm.GetElements(typeof(ISqlPrimaryKeyConstraint), 0);
                IEnumerable<IModelElement> fkcs = ssm.GetElements(typeof(ISqlForeignKeyConstraint), 0);
                IEnumerable<IModelElement> ukcs = ssm.GetElements(typeof(ISqlUniqueConstraint), 0);

                // Only store 2-part name, or unnamed  ie local stuff.

                _IndexesCache           = idxs.Cast<ISqlIndex>()                .Where(n => n.Name != null && (n.Name.ExternalParts == null || n.Name.ExternalParts.Count == 0)).Select(n => n);
                _PrimaryKeyConstraints  = pkcs.Cast<ISqlPrimaryKeyConstraint>() .Where(n => (n.DefiningTable.Name.ExternalParts == null || n.DefiningTable.Name.ExternalParts.Count == 0)).Select(n => n);
                _ForeignKeyConstraints  = fkcs.Cast<ISqlForeignKeyConstraint>() .Where(n => (n.DefiningTable.Name.ExternalParts == null || n.DefiningTable.Name.ExternalParts.Count == 0)).Select(n => n);
                _UniqueConstraints      = ukcs.Cast<ISqlUniqueConstraint>()     .Where(n => (n.DefiningTable.Name.ExternalParts == null || n.DefiningTable.Name.ExternalParts.Count == 0)).Select(n => n);
 
            }
            else
            {
                // bump value - until we have clear elapsed window of xx secs don't refresh
                _LastConstraintsAndIndexesCacheRefresh = DateTime.Now;
            }

        }
        // refresh the table/view/column cache if we're running the insertion column checks rule.
        // the timing ought to be enough to allow a refresh if we load a new project 
        // or alter an existing one.
        //        public static void RefreshColumnCache(DataSchemaModel ssm)
        public static void RefreshColumnCache(ModelStore ssm)
        {
            // in case initialiser doesn't work.
            if (_LastInsertColumnCacheRefresh == null ||
                 DateTime.Compare(_LastInsertColumnCacheRefresh.Add(TimeSpan.FromSeconds(_CacheRefreshIntervalSeconds)), DateTime.Now) == -1
               )
            {
                ts = ssm.GetElements(typeof(ISqlTable), 0);
                vs = ssm.GetElements(typeof(ISqlView), 0);

                // Only store 2-part name ie local tables.
                tables = ts.Cast<ISqlTable>().Where(n => n.Name.ExternalParts == null || n.Name.ExternalParts.Count == 0).Select(n => n);
                views = vs.Cast<ISqlView>().Where(n => n.Name.ExternalParts == null || n.Name.ExternalParts.Count == 0).Select(n => n);

                //if (_InsertStatementOptionalColumnsCache == null)
                //{
                //    if (SqlComparer.Comparer == null)
                //    {
                //        throw new System.ArgumentException("SqlComparer.Comparer is null");
                //    }
                //    _InsertStatementOptionalColumnsCache = new Dictionary<String, List<String>>((SqlComparer.Comparer));
                //}
                //if (_InsertStatementMandatoryColumnsCache == null)
                //{
                //    if (SqlComparer.Comparer == null)
                //    {
                //        throw new System.ArgumentException("SqlComparer.Comparer is null");
                //    }
                //    _InsertStatementMandatoryColumnsCache = new Dictionary<String, List<String>>((SqlComparer.Comparer));
                //}
                if (_tablesColumnsCache == null)
                {
                    if (SqlComparer.Comparer == null)
                    {
                        throw new System.ArgumentException("SqlComparer.Comparer is null");
                    }
                    _tablesColumnsCache = new Dictionary<String, List<ISqlSimpleColumn>>((SqlComparer.Comparer));
                }

                _tablesColumnsCache.Clear();
                //_InsertStatementOptionalColumnsCache.Clear();
                //_InsertStatementMandatoryColumnsCache.Clear();

                foreach (var tbl in tables)
                {
                    List<ISqlSimpleColumn> columns = new List<ISqlSimpleColumn>();
                    //List<String> optional = new List<String>();
                    //List<String> mandatory = new List<String>();
                    // The semantics of the include/excludes here together with CheckBothNullsAndDefaultable
                    // are all a bit smelly.
                    foreach (var c in tbl.Columns)
                    {
                        ISqlSimpleColumn simpleCol = c as ISqlSimpleColumn;
                        if (simpleCol != null)
                        {
                            columns.Add(simpleCol);
                        }

                        //if (simpleCol != null
                        //&& simpleCol.IsIdentity == false
                        //&& simpleCol.IsRowGuidColumn == false
                        //&& ((simpleCol.IsNullable == true)    // always checked - if we're checking
                        //   || (simpleCol.Defaults.Count != 0 && CheckBothNullsAndDefaultable)
                        //   )
                        //   )
                        //{
                        //    optional.Add(simpleCol.Name.Parts[2]);
                        //}
                        //if (simpleCol != null
                        //&& simpleCol.IsIdentity == false
                        //&& simpleCol.IsRowGuidColumn == false
                        //&& ((simpleCol.IsNullable == false && simpleCol.Defaults.Count == 0)    // always checked - if we're checking
                        //   )
                        //   )
                        //{
                        //    mandatory.Add(simpleCol.Name.Parts[2]);
                        //}
                    }
                    String schema = tbl.Name.Parts[0];
                    String tblName = tbl.Name.Parts[1];
                    String key = schema + @"." + tblName;

                    _tablesColumnsCache.Add(key, columns);
                    //_InsertStatementOptionalColumnsCache.Add(key, optional);
                    //_InsertStatementMandatoryColumnsCache.Add(key, mandatory);

                }

                foreach (var view in views)
                {
                    //List<String> optional = new List<String>();
                    //List<String> mandatory = new List<String>();
                    // The semantics of the include/excludes here together with CheckBothNullsAndDefaultable
                    // are all a bit smelly.
                    foreach (var c in view.Columns)
                    {
                        ISqlSimpleColumn simpleCol = c as ISqlSimpleColumn;
                        //if (simpleCol != null
                        //&& simpleCol.IsIdentity == false
                        //&& simpleCol.IsRowGuidColumn == false
                        //&& ((simpleCol.IsNullable == true)    // always checked - if we're checking
                        //   || (simpleCol.Defaults.Count != 0 && CheckBothNullsAndDefaultable)
                        //   )
                        //   )
                        //{
                        //    optional.Add(simpleCol.Name.Parts[2]);
                        //}
                        //if (simpleCol != null
                        //&& simpleCol.IsIdentity == false
                        //&& simpleCol.IsRowGuidColumn == false
                        //&& ((simpleCol.IsNullable == false && simpleCol.Defaults.Count == 0)    // always checked - if we're checking
                        //   )
                        //   )
                        //{
                        //    mandatory.Add(simpleCol.Name.Parts[2]);
                        //}
                    }
                    String schema = view.Name.Parts[0];
                    String tblName = view.Name.Parts[1];
                    String key = schema + @"." + tblName;
                    //_InsertStatementOptionalColumnsCache.Add(key, optional);
                    //_InsertStatementMandatoryColumnsCache.Add(key, mandatory);
                }
                _LastInsertColumnCacheRefresh = DateTime.Now;
            }
            else
            {
                // bump value - until we have clear elapsed window of xx secs don't refresh
                _LastInsertColumnCacheRefresh = DateTime.Now;
            }
        }


        public static void LoadSettings()
        {
            // in case initialiser doesn't work.
            if (_LastNiniRefresh == null ||
                 DateTime.Compare(_LastNiniRefresh.Add(TimeSpan.FromSeconds(_CacheRefreshIntervalSeconds)), DateTime.Now) == -1
               )
            {

                XmlConfigSource source = new XmlConfigSource();
                source.Load(DotNetConfigSource.GetFullConfigPath());


                ////////////////////////////////////////
                {
                    /*
                      <Section Name="ModelCache">
                        <Key Name="RefreshInterval"        Value="15" />
                      </Section>
                    */
                    String rx = source.Configs["ModelCache"].GetString("RefreshInterval");
                    _CacheRefreshIntervalSeconds = Int32.Parse(rx);
                }
                ////////////////////////////////
                {
                    ExcludedVariableTypesForNameLengthRuleIncludesTable = false;

                    String rx = source.Configs["VariableSettings"].GetString("MinimumLengthIsExcludedForTheseDataTypes");
                    string[] arx = rx.Split(new Char[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
                    List<string> lrx = new List<string>();
                    for (int i = 0; i < arx.Count(); i++)
                    {
                        arx[i].Trim(new Char[] { ' ', '\t', '\n', '\r' });
                        if (!String.IsNullOrWhiteSpace(arx[i]))
                        {
                            lrx.Add(arx[i]);
                            if (arx[i].ToLowerInvariant() == @"table") { ExcludedVariableTypesForNameLengthRuleIncludesTable = true; }
                        }
                    }
                    ExcludedVariableTypesForNameLengthRule = lrx.ToArray();
                    MinimumVariableNameLength = source.Configs["VariableSettings"].GetInt("MinimumLengthOfName");
                }
                //////////////////////////////
                {
                    String rx = source.Configs["ParameterSettings"].GetString("MinimumLengthIsExcludedForTheseDataTypes");
                    string[] arx = rx.Split(new Char[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
                    List<string> lrx = new List<string>();
                    for (int i = 0; i < arx.Count(); i++)
                    {
                        arx[i].Trim(new Char[] { ' ', '\t', '\n', '\r' });
                        if (!String.IsNullOrWhiteSpace(arx[i]))
                        {
                            lrx.Add(arx[i]);
                        }
                    }
                    ExcludedParameterTypesForNameLengthRule = lrx.ToArray();
                    MinimumParameterNameLength = source.Configs["ParameterSettings"].GetInt("MinimumLengthOfName");
                }
                //////////////////////////////
                {
                    /*
                      <Section Name="ParameterAndVariableSettings">
                        <Key Name="ValidNameRegExp"        Value="(?:^.*$)" />
                      </Section>
                    */
                    String rx = source.Configs["ParameterAndVariableSettings"].GetString("ValidNameRegExp");
                    ValidParameterAndVariableRegexp = rx;
                }

                //////////////////////////////
                {
                    /*
                          <Section Name="KeyClusteringOptions">
                            <Key Name="AllowClusterOnPrimaryKey"  Value="True" />
                            <Key Name="AllowClusterOnForeignKey"  Value="True" />
                          </Section>

                    */
                    String s_AllowClusterOnPrimaryKey = source.Configs["KeyClusteringOptions"].GetString("AllowClusterOnPrimaryKey");
                    String s_AllowClusterOnForeignKey = source.Configs["KeyClusteringOptions"].GetString("AllowClusterOnForeignKey");
                    if (s_AllowClusterOnPrimaryKey == @"True") { AllowClusterOnPrimaryKey = true; } else { AllowClusterOnPrimaryKey = false; };
                    if (s_AllowClusterOnForeignKey == @"True") { AllowClusterOnForeignKey = true; } else { AllowClusterOnForeignKey = false; };
                }
                //////////////////////////////
                {
                    /*
                      <Section Name="IndexOptions">
                        <Key Name="MaximumNoOfIndexColumns"  Value="2" />
                      </Section>

                    */
                    String s_MaximumNoOfIndexColumns = source.Configs["IndexOptions"].GetString("MaximumNoOfIndexColumns");
                    MaximumNoOfIndexColumns = Int32.Parse(s_MaximumNoOfIndexColumns);

                }
                //////////////////////////////
                _LastNiniRefresh = DateTime.Now;
            }
            else
            {
                // bump value - until we have clear elapsed window of xx secs don't refresh
                _LastNiniRefresh = DateTime.Now;
            }
        }
    }
}


